//---------------------------------------------------------------------
//  This file is part of the Background Motion solution.
// 
//  Copyright (C) Mindscape (TM).  All rights reserved.
//  http://www.mindscape.co.nz
// 
//  THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//---------------------------------------------------------------------

using System;

using Microsoft.Practices.EnterpriseLibrary.Validation;
using Mindscape.BackgroundMotion.Core.Model;
using Mindscape.BackgroundMotion.Core.Utilities;
using Mindscape.BackgroundMotion.Core.Validators;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace Mindscape.BackgroundMotion.Model
{
  /// <summary>
  /// Represents a member of the system
  /// </summary>
  [Table(Name = "Member")]
  public sealed class Member : ModelObject
  {
    // This is a fixed salt used for additional entropy against the passwords
    public const string Salt = "M1ndscape!";

    private int _id;

    private string _email;
    private string _passwordHash;
    private string _username;
    private string _hashCookie;

    private DateTime _lastActiveDate;
    private DateTime _lastUpdatedDate;

    private bool _locked;
    private bool _isModerator;

    private EntitySet<Comment> _comments;
    private EntitySet<Contribution> _approvedContributions;
    private EntitySet<Contribution> _contributions;

    /// <summary>
    /// Initializes the relationships for a Member
    /// </summary>
    public Member()
    {
      _comments = CreateEntitySet<Comment>(
        (Comment c) => c.Member = null,
        (Comment c) => c.Member = this
      );

      _approvedContributions = CreateEntitySet<Contribution>(
        (Contribution c) => c.ApprovedBy = null,
        (Contribution c) => c.ApprovedBy = this
      );

      _contributions = CreateEntitySet<Contribution>(
        (Contribution c) => c.Contributor = null,
        (Contribution c) => c.Contributor = this
      );

      _lastActiveDate = DateTime.Now;
      _lastUpdatedDate = DateTime.Now;
    }

    /// <summary>
    /// Authenticates against the stored password hash
    /// </summary>
    /// <param name="password">The plaintext password to assess against the hash</param>
    /// <returns>true if the password matches, else false</returns>
    public bool Authenticate(string password)
    {
      return (PasswordHash == Hasher.GetSHA1Hash(password + Salt));
    }

    /// <summary>
    /// Validates this instance for a correct state
    /// </summary>
    /// <remarks>
    /// This is called by the Enterprise Library Validation framework
    /// </remarks>
    /// <param name="results">A set of ValidationResults indicating validation issues with the current instance</param>
    protected override void Validate(ValidationResults results)
    {
      results.AddAllResults(Validation.ValidateFromAttributes(this));

      // Handle pre-insert validation
      if (Id == 0)
      {
        using (UnitOfWork.Begin())
        {
          Member member = Repository<Member>.FindOne(m => m.Email == Email);
          if (member != null)
          {
            results.AddResult(new ValidationResult("Email address already in use, please use another", null, null, null, null));
          }
        }
      }
    }

    /// <summary>
    /// Updates the cookie reference which can be used for digest authentication of this member
    /// </summary>
    private void UpdateHashCookie()
    {
      HashCookie = Hasher.GetSHA1Hash(Email + PasswordHash + Salt);
    }

    #region Mapped Properties

    [Column(Storage = "_id", IsPrimaryKey = true, IsDbGenerated = true)]
    public override int Id
    {
      get { return _id; }
      set
      {
        if (_id != value)
        {
          OnPropertyChanging("Id");
          _id = value;
          OnPropertyChanged("Id");
        }
      }
    }

    [RequireValidator("Email", 100)]
    [EmailValidator("Email")]
    [Column(Storage = "_email")]
    public string Email
    {
      get { return _email; }
      set
      {
        if ((_email != value))
        {
          OnPropertyChanging("Email");
          _email = value;
          OnPropertyChanged("Email");

          if (Email != null)
          {
            UpdateHashCookie();
          }
        }
      }
    }

    [RequireValidator("Password", 40)]
    [Column(Storage = "_passwordHash")]
    public string PasswordHash
    {
      get { return _passwordHash; }
      set
      {
        if ((_passwordHash != value))
        {
          OnPropertyChanging("PasswordHash");
          _passwordHash = value;
          OnPropertyChanged("PasswordHash");
        }
      }
    }

    [RequireDateTimeValidator("Last Active Date")]
    [Column(Storage = "_lastActiveDate")]
    public DateTime LastActiveDate
    {
      get { return _lastActiveDate; }
      set
      {
        if ((_lastActiveDate != value))
        {
          OnPropertyChanging("LastActiveDate");
          _lastActiveDate = value;
          OnPropertyChanged("LastActiveDate");
        }
      }
    }

    [RequireDateTimeValidator("Last Updated Date")]
    [Column(Storage = "_lastUpdatedDate")]
    public DateTime LastUpdatedDate
    {
      get { return _lastUpdatedDate; }
      set
      {
        if ((_lastUpdatedDate != value))
        {
          OnPropertyChanging("LastUpdatedDate");
          _lastUpdatedDate = value;
          OnPropertyChanged("LastUpdatedDate");
        }
      }
    }

    [Column(Storage = "_locked")]
    public bool Locked
    {
      get { return _locked; }
      set
      {
        if ((_locked != value))
        {
          OnPropertyChanging("Locked");
          _locked = value;
          OnPropertyChanged("Locked");
        }
      }
    }

    [RequireValidator("Username", 50)]
    [Column(Storage = "_username")]
    public string Username
    {
      get { return _username; }
      set
      {
        if ((_username != value))
        {
          OnPropertyChanging("Username");
          _username = value;
          OnPropertyChanged("Username");
        }
      }
    }

    [Column(Storage = "_isModerator")]
    public bool IsModerator
    {
      get { return _isModerator; }
      set
      {
        if ((_isModerator != value))
        {
          OnPropertyChanging("IsModerator");
          _isModerator = value;
          OnPropertyChanged("IsModerator");
        }
      }
    }

    [Column(Storage = "_hashCookie")]
    public string HashCookie
    {
      get { return _hashCookie; }
      set
      {
        if ((_hashCookie != value))
        {
          OnPropertyChanging("HashCookie");
          _hashCookie = value;
          OnPropertyChanged("HashCookie");
        }
      }
    }

    [Association(Storage = "_comments", OtherKey = "MemberId")]
    public EntitySet<Comment> Comments
    {
      get { return _comments; }
      set { _comments.Assign(value); }
    }

    [Association(Storage = "_approvedContributions", OtherKey = "ApprovedById")]
    public EntitySet<Contribution> ApprovedContributions
    {
      get { return _approvedContributions; }
      set { _approvedContributions.Assign(value); }
    }

    [Association(Storage = "_contributions", OtherKey = "ContributorId")]
    public EntitySet<Contribution> Contributions
    {
      get { return _contributions; }
      set { _contributions.Assign(value); }
    }

    #endregion

    /// <summary>
    /// The stored password hash for this instance
    /// </summary>
    public string Password
    {
      get { return PasswordHash; }
      set
      {
        if (!string.IsNullOrEmpty(value))
        {
          PasswordHash = Hasher.GetSHA1Hash(value + Salt);
          UpdateHashCookie();
        }
      }
    }
  }
}